home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / osr5 / sco / scripts / kshfuncs / ucd < prev   
Encoding:
Text File  |  1997-08-26  |  12.8 KB  |  312 lines

  1. # @(#) ucd.ksh 3.3 97/07/13
  2. # 1991 john@armory.com
  3. # 92/12/22 changed to autoloaded function
  4. # 93/12/18 Added conversion of leading $HOME to ~
  5. # 94/01/07 Consider constant part of prompt when deciding how long to
  6. #          allow $PWDS to be.
  7. # 94/10/28 Added lookupdir (directory lookup capability)
  8. # 95/09/17 Fixed bug that prevented a directory less than -20 being given.
  9. #          Added debug capability.
  10. # 95/09/25 Search for only the first component of the given path.
  11. # 96/01/02 Fixed dir index range check.
  12. # 97/02/11 If a filename is given, cd to the directory it is in.
  13. # 97/07/13 3.3 Added search for dir in args if >2 are given.
  14. #          Let various paremeters be customized.
  15. #
  16. # Usage:
  17. # alias cd=ucd
  18. # cd -n        cd to n'th last directory
  19. # cd -s        cd to the last directory that contains substring s
  20. # cd abs-path    If a path beginning in /, ./, or ../ is given, it will be
  21. #        cd'd to if it is a directory.  If it is not a directory but is
  22. #        when the trailing path component is removed, that is cd'd to
  23. #        instead.  This is a convenience to let a filename be given to
  24. #        cd to the directory it is in without having to edit it to
  25. #        remove the trailing component.
  26. # cd non-abs-path    Standard ksh cd behaviour, except that if the path
  27. #        is not found in CDPATH, it is searched for in a set of
  28. #        directory databases (if any are set).  Directory lookup depends
  29. #        on the availability of the 'look' or 'nlook' utilities.  See
  30. #        "Directory Lookup" below for a description.
  31. # cd s s    Standard ksh cd behaviour (substring replacement); see ksh
  32. #        ksh man page.
  33. # cd s s s ...    If 3 or more arguments are given, each is checked in order to
  34. #        determine whether it is a directory.  An attempt is made to cd
  35. #        to the first that is a directory.  To do this for only two
  36. #        directories, give an extra bogus name that is not a directory,
  37. #        e.g. ''   Example: cd '' foo bar
  38. # dirs [num]    List directories in directory history list.  dirs is stored in
  39. #        a separate file (not this one) so that it can be autoloaded
  40. #        when first invoked.
  41. #
  42. # Directory Lookup:
  43. # Database format:
  44. # When this file is first sourced, if the shell variable _dirfiles is set it
  45. # is taken to be the names of a whitespace-separated set of directory
  46. # databases.  If not, a file named .dirs is searched for in the user's home
  47. # directory, and if it exists, is taken to be the name of a directory database.
  48. # The format of the directory databases is one pair of elements on each line:
  49. # trailing-dir-component full-path
  50. # If full-path does not begin with /, it is converted to an absolute path by
  51. # prepending it with the name of the directory that the directory database
  52. # resides in.
  53. # Example:
  54. # ksh lib/ksh
  55. # If the directory database that the above example line came from resides in
  56. # /u/spcecdt, the full-path is taken to be /u/spcecdt/lib/ksh.
  57. # Suitable directory databases can be built with the 'mkdirlist' utility.
  58. # Operation:
  59. # When a single argument that does not begin with /, ./, or ../ is given and
  60. # it is not found by a normal directory search (in the current directory or
  61. # in each component of CDPATH, if set), it will be searched for in the
  62. # directory databases.  The search goes like this:
  63. # If the directory name given exactly matches one trailing-directory-component
  64. # in the first directory database, the full-path will be cd'd to.  If it
  65. # matches more than one trailing-directory-component, the matches (up to 10)
  66. # are offered for interactive selection.  If there is no match, the directory
  67. # name given is searched for as a prefix of any trailing-dir-component, and the
  68. # results are treated the same way (immediate cd if one match, interactive
  69. # selection if more).  If there are still no matches, the next directory
  70. # database (if any) is searched in the same manner.
  71. # Putting a trailing '/' on the directory name to be searched for will
  72. # suppress the search for prefixes; only an exact match (to the name without
  73. # the trailing '/') will be accepted.
  74. # In all cases, the full-path returned by a match is tested for whether it
  75. # is an acceptable cd target; if it does not exist, is not a directory, or
  76. # is not executable it is skipped (not included in the matches or match count).
  77. # If more than one component is given (e.g. foo/bar), the first component is
  78. # looked up and the other components are attached to the full-path of each
  79. # match before it is tested for whether it is an acceptable cd target.
  80. #
  81. # Shell variables:
  82. #      This function sets PWDS to PWD, but with an upper limit on how
  83. # long it is (removing directory components from the left-hand-side as
  84. # neccessary), so that $PWDS can be used in PS1 instead of $PWD.
  85. # The maximum length of PWDS may be set at any time by setting the _ucdmaxpwds
  86. # variable.  The default is 50.  PWDS is trimmed to the trailing _ucdmaxpwds
  87. # characters of PWD, then any remaining piece of the first component is
  88. # removed.  Changes to _ucdmaxpwds affect PWDS the next time ucd is invoked.
  89. #      If UCDDEBUG is set to a non-null value, debugging information is printed.
  90. #      If _ucdnumdirs is set BEFORE this file is sourced (before the first time
  91. # ucd is invoked, if you are using FPATH), it sets the number of history slots.
  92. # The default and maximum is 1023 (due to ksh array limits).
  93. #      _ucdmaxmatch may be set at any time to the maximum number of matches
  94. # that should be offered for interactive selection.  More than this number of
  95. # matches will cause a complaint to be issued and termination of cd processing.
  96. # The default is 10.
  97.  
  98. # This file (and ucd) can be put in $FPATH/ucd so that they will be autoloaded
  99. # instead of sourcing them directly from .profile or $ENV (e.g. .kshrc).
  100. # See the description of FPATH in the ksh man page.
  101.  
  102.  
  103. typeset -i _ucdnumdirs _dind _ucdmaxmatch _ucdmaxpwds _ucdoldmp=0
  104.     typeset -i _ucdi=0
  105. # even after typeset -i, value will be null until assigned to;
  106. # also, if it had a non-numeric value, will become null after typeset -i
  107. [ -z "$_ucdnumdirs" ] && _ucdnumdirs=1023
  108. [ -z "$_ucdmaxmatch" ] && _ucdmaxmatch=10
  109. [ -z "$_ucdmaxpwds" ] && _ucdmaxpwds=50
  110.  
  111. function ucd {
  112.     typeset pwdhead debug
  113.  
  114.     [ -n "$UCDDEBUG" ] && debug=true || debug=false
  115.     # Retrieve dir from history
  116.     if [[ $# -eq 1 && "$1" = -?* ]]; then
  117.     $debug && print -r -u2 "Searching directory history..."
  118.     if [[ "$1" = -+([0-9]) ]]; then
  119.         typeset -i ind=$1
  120.         if [ ind -lt -_ucdnumdirs -o ind -gt -1 ]; then
  121.         print -u2 \
  122.     "Bad dir history index ($ind); must be in the range -1..-$_ucdnumdirs."
  123.         return 1
  124.         fi
  125.         let ind=_dind+$1+1
  126.         if [ ind -lt 0 ]; then let ind+=_ucdnumdirs; fi
  127.         if [ -z "${_olddirs[ind]}" ]; then
  128.         print -u2 "No such old dir."
  129.         return 1
  130.         fi
  131.         $debug && print -u2 "Trying to cd to dir #$ind (${_olddirs[ind]})"
  132.         \cd "${_olddirs[ind]}" || return $?
  133.     else
  134.         typeset -i i=1 ind=_dind
  135.         typeset dir pat=${1#-}
  136.  
  137.         $debug && print -u2 \
  138.         "Searching previous dir list for a directory matching '$pat'"
  139.         while [ i -le _ucdnumdirs ]; do
  140.         dir=${_olddirs[ind]}
  141.         $debug && print -n -u2 "#$ind:$dir "
  142.         if [[ "$dir" = ?(*$pat*|) ]]; then break; fi
  143.         let ind-=1
  144.         if [ ind -lt 0 ]; then ind=_ucdnumdirs-1; fi
  145.         let i+=1
  146.         done
  147.         $debug && print -u2 ""
  148.         if [ i -gt _ucdnumdirs -o -z "$dir" ]; then
  149.         print -u2 "No old dir matches that pattern."
  150.         else
  151.         \cd "$dir" || return $?
  152.         fi
  153.     fi
  154.     elif [ $# -gt 2 ]; then
  155.     $debug && print -r -u2 "Searching args for a directory..."
  156.     # If more than 2 args are given, cd to the first arg that is a dir.
  157.     typeset d
  158.     for d; do
  159.         [ -d "$d" ] && break
  160.     done
  161.     [ -d "$d" ] || return 1
  162.     \cd "$d" || return 1
  163.     elif [[ $# -gt 1 || "$1" = ?(.|..)/* || -z "$_dirfiles" || \
  164.     ! -r "$_dirfiles" ]]; then
  165.     # if a substitution is being done, an absolute path is given,
  166.     # or there is no dirlist file, do cd w/o errors discarded because it is
  167.     # the only thing that will be tried.
  168.     # If a single arg that is an absolute path is given and it is not
  169.     # a directory, but it is if the trailing component is stripped,
  170.     # cd to that instead.  This is a convenience to let a filename be
  171.     # given to cd to the directory it is in without having to edit it
  172.     # to remove the trailing component.
  173.     if [[ $# -eq 1 && "$1" = ?(.|..)/* && ! -d "$1" && -d "${1%%*([!/])}" ]]
  174.     then
  175.         set -- "${1%%*([!/])}"
  176.         print -u2 "ucd: changing to parent dir: $1"
  177.     fi
  178.     \cd "$@" || return 1
  179.     else
  180.     # Try plain cd first.  Discard errors because dir lookup will be
  181.     # attempted if dir is not in path (saving error output w/pipe uses an
  182.     # exec).
  183.     \cd "$@" 2>|/dev/null || {
  184.         # If dir was not in path, try looking it up; if found, try to
  185.         # cd to it.  If not found, do original cd again w/o error
  186.         # output redirected, to get an error message.  Only return 1 if
  187.         # this final cd fails, just in case it succeeds even though it
  188.         # didn't last time.
  189.         [ -n "$_look" ] && lookupdir "$1" "$debug" &&
  190.         \cd "$lookupdir_ret" || \cd "$@" || return 1
  191.     }
  192.     fi
  193.     _dind='(_dind+1)%_ucdnumdirs'
  194.     _olddirs[_dind]=$OLDPWD
  195.     # Strip off leading $HOME
  196.     [[ $HOME != / && "$PWD" = $HOME?(/*) ]] &&
  197.     pwd="~${PWD#$HOME}" || pwd=$PWD
  198.     # If prompt would be too long with constant part of prompt added,
  199.     # get rid of some of the leading part of the path.
  200.     pwdhead=$pwd$cprompt
  201.     # If this is the first invokation of ucd, or if _ucdmaxpwds has changed,
  202.     # generate _ucdtail from it.
  203.     if [ _ucdmaxpwds -ne _ucdoldmp ]; then
  204.     typeset -i _ucdi=0
  205.     _ucdtail=
  206.     while [ _ucdi -lt _ucdmaxpwds ]; do
  207.         _ucdtail="$_ucdtail?"
  208.         let _ucdi=_ucdi+1
  209.     done
  210.     _ucdoldmp=_ucdmaxpwds
  211.     unset _ucdi
  212.     fi
  213.     eval pwdhead=\${pwdhead%%$_ucdtail}
  214.     [ -n "$pwdhead" ] && PWDS=${pwd##$pwdhead*([!/])/} || PWDS=$pwd
  215.     return 0
  216. }
  217.  
  218. [ -z "$_dirfiles" -a -r $HOME/.dirs ] && _dirfiles=$HOME/.dirs
  219.  
  220. # nlook is faster & less buggy than look
  221. if type nlook >|/dev/null; then
  222.     _look=nlook
  223. elif type look >|/dev/null; then
  224.     _look=look
  225. else
  226.     print -ru2 -- "ucd: look: not found.  Directory lookup disabled."
  227. fi
  228.  
  229. # Usage: lookupdir dirname/ debugstatus  or   lookupdir dirprefix debugstatus
  230. # If dirname/ is given, dirname as the full last component of a path is
  231. # searched for.  If dirname is given, dirname is first searched for as above,
  232. # and if it isn't found, any last component of a path that begins with dirname
  233. # is searched for.  This is done for each dirlist file in turn.  If _dirfiles
  234. # is not set or dir is not found, failure status is returned.
  235. # Note that the ambiguity check is only done on a single-file basis.
  236. # The dir file is built by 'mkdirlist' and has this format:
  237. # trailing-dir-component full-path
  238. # lookupdir should not be called with an absolute path (one that starts with /)
  239. function lookupdir {
  240.     typeset SearchDir=$1 DirTail= debug=$2 Srch S dir file found exact=false
  241.     typeset -i i=0 matches
  242.  
  243.     if [[ "$SearchDir" = */ ]]; then
  244.     exact=true
  245.     SearchDir=${SearchDir%/}
  246.     fi
  247.     # If dir has more than one component, split off the first component so it
  248.     # can be looked up.  Keep the rest (inc. / at front) so it can be added.
  249.     if [[ "$SearchDir" = */* ]]; then
  250.     DirTail=${SearchDir##*([!/])}
  251.     SearchDir=${SearchDir%%/*}
  252.     exact=true
  253.     fi
  254.     # If dirname/ was given, get rid of the / and instead use a trailing space
  255.     # since that is what the file contains.  If dirname is given, make the args
  256.     # be: first, the dirname with a trailing space (so that an exact match will
  257.     # be preferred), then just the dirname.
  258.     $exact && set -A S -- "$SearchDir " ||
  259.     set -A S -- "$SearchDir " "$SearchDir"
  260.  
  261.     for file in $_dirfiles; do
  262.     for Srch in "${S[@]}"; do
  263.         set -- $($_look "$Srch" "$file")
  264.         i=0
  265.         $debug && print -u2 "Searching dirlist '$file' for '$Srch'; got: $*"
  266.         # Canonize dirs & get rid of dirs not accessible or which no longer
  267.         # exist.  If the given path had more then one component, check for
  268.         # accessibility of the full path.
  269.         while [ $# -gt 0 ]; do
  270.         # If dir is relative, prefix it with dir that dir file resides
  271.         # in.  Discard 1 or more /'s after the last dir component since
  272.         # file might be //file (e.g. $HOME=/, file=//.dirs).
  273.         # Get rid of any ./ prefix in dir.
  274.         [[ "$2" = /* ]] && dir=$2 || dir=${file%%+(/)!(/)}/${2#./}
  275.         dir="$dir$DirTail"
  276.         $debug && print -u2 "Checking $dir"
  277.         if [ -d "$dir" -a -x "$dir" ]; then
  278.             found[i]=$dir
  279.             let i+=1
  280.         fi
  281.         shift 2
  282.         done
  283.         # If we got anything, do not search for any other search strings,
  284.         # because those that come earlier are preferred.
  285.         [ i -gt 0 ] && break 2
  286.     done
  287.     done
  288.     $debug && print -u2 "Got $i matches."
  289.     [ i -eq 0 ] && return 1
  290.     matches=i
  291.     if [ matches -gt 1 ]; then
  292.     # Adjust the number below to set the max number of dirs presented to
  293.     # select from.
  294.     if [ matches -le _ucdmaxmatch ]; then
  295.         print -u2 -- \
  296.     "$SearchDir: ambiguous.  Enter a number to select a dir or q to skip."
  297.         unset lookupdir_ret
  298.         select lookupdir_ret in "${found[@]}"; do
  299.         break
  300.         done
  301.         [ -n "$lookupdir_ret" ]
  302.         return $?
  303.     else
  304.         print -u2 -- "$SearchDir: ambiguous ($matches matches)."
  305.         return 1
  306.     fi
  307.     else
  308.     lookupdir_ret=${found[0]}
  309.     return 0
  310.     fi
  311. }
  312.